clipboard: Refactor gdk_clipboard_read() to be async
authorBenjamin Otte <otte@redhat.com>
Wed, 22 Nov 2017 08:25:35 +0000 (09:25 +0100)
committerBenjamin Otte <otte@redhat.com>
Sun, 3 Dec 2017 04:46:26 +0000 (05:46 +0100)
This allows us not just to pass any mime type to the read function, but
it also makes it possible to pass multiple mime types and the clipboard
can then try them in order until it finds a supported one.

This is so far not implemented though.

gdk/gdkclipboard.c
gdk/gdkclipboard.h
gdk/gdkclipboardprivate.h
gdk/x11/gdkclipboard-x11.c
gdk/x11/gdkselectioninputstream-x11.c
gdk/x11/gdkselectioninputstream-x11.h
tests/testclipboard2.c

index eee9284a9a4365a4ff17a1ced8f5185a06ac7d79..30b81fff3e4fea43ec3eaf54dcafde6095f2f55f 100644 (file)
@@ -113,9 +113,30 @@ gdk_clipboard_finalize (GObject *object)
   G_OBJECT_CLASS (gdk_clipboard_parent_class)->finalize (object);
 }
 
+static void
+gdk_clipboard_real_read_async (GdkClipboard        *clipboard,
+                               GdkContentFormats   *formats,
+                               int                  io_priority,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+  GTask *task;
+
+  task = g_task_new (clipboard, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gdk_clipboard_read_async);
+  g_task_set_task_data (task, gdk_content_formats_ref (formats), (GDestroyNotify) gdk_content_formats_unref);
+
+  g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Reading local content not supported yet.");
+  g_object_unref (task);
+}
+
 static GInputStream *
-gdk_clipboard_real_read (GdkClipboard *clipboard,
-                         const char   *mime_type)
+gdk_clipboard_real_read_finish (GdkClipboard  *clipboard,
+                                const char   **out_mime_type,
+                                GAsyncResult  *result,
+                                GError       **error)
 {
   /* whoop whooop */
   return g_memory_input_stream_new ();
@@ -130,7 +151,8 @@ gdk_clipboard_class_init (GdkClipboardClass *class)
   object_class->set_property = gdk_clipboard_set_property;
   object_class->finalize = gdk_clipboard_finalize;
 
-  class->read = gdk_clipboard_real_read;
+  class->read_async = gdk_clipboard_real_read_async;
+  class->read_finish = gdk_clipboard_real_read_finish;
 
   /**
    * GdkClipboard:display:
@@ -237,17 +259,84 @@ gdk_clipboard_get_formats (GdkClipboard *clipboard)
   return priv->formats;
 }
 
-GInputStream *
-gdk_clipboard_read (GdkClipboard *clipboard,
-                    const char   *mime_type)
+static void
+gdk_clipboard_read_internal (GdkClipboard        *clipboard,
+                             GdkContentFormats   *formats,
+                             int                  io_priority,
+                             GCancellable        *cancellable,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
 {
-  GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (clipboard);
+  return GDK_CLIPBOARD_GET_CLASS (clipboard)->read_async (clipboard,
+                                                          formats,
+                                                          io_priority,
+                                                          cancellable,
+                                                          callback,
+                                                          user_data);
+}
+
+/**
+ * gdk_clipboard_read_async:
+ * @clipboard: a #GdkClipboard
+ * @mime_types: a %NULL-terminated array of mime types to choose from
+ * @io_priority: the [I/O priority][io-priority]
+ * of the request. 
+ * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
+ * @callback: (scope async): callback to call when the request is satisfied
+ * @user_data: (closure): the data to pass to callback function
+ *
+ * Asynchronously requests an input stream to read the @clipboard's
+ * contents from. When the operation is finished @callback will be called. 
+ * You can then call gdk_clipboard_read_finish() to get the result of the 
+ * operation.
+ *
+ * The clipboard will choose the most suitable mime type from the given list
+ * to fulfill the request, preferring the ones listed first. 
+ **/
+void
+gdk_clipboard_read_async (GdkClipboard        *clipboard,
+                          const char         **mime_types,
+                          int                  io_priority,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+  GdkContentFormats *formats;
+
+  g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
+  g_return_if_fail (mime_types != NULL && mime_types[0] != NULL);
+  g_return_if_fail (callback != NULL);
+
+  formats = gdk_content_formats_new (mime_types, g_strv_length ((char **) mime_types));
+
+  gdk_clipboard_read_internal (clipboard, formats, io_priority, cancellable, callback, user_data);
+
+  gdk_content_formats_unref (formats);
+}
 
+/**
+ * gdk_clipboard_read_finish:
+ * @clipboard: a #GdkClipboard
+ * @out_mime_type: (out) (allow-none) (transfer none): pointer to store
+ *     the chosen mime type in or %NULL
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occurring, or %NULL to 
+ * ignore.
+ *
+ * Finishes an asynchronous clipboard read started with gdk_clipboard_read_async().
+ *
+ * Returns: (transfer full): a #GInputStream or %NULL on error.
+ **/
+GInputStream *
+gdk_clipboard_read_finish (GdkClipboard  *clipboard,
+                           const char   **out_mime_type,
+                           GAsyncResult  *result,
+                           GError       **error)
+{
   g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
-  g_return_val_if_fail (mime_type != NULL, NULL);
-  g_return_val_if_fail (gdk_content_formats_contain_mime_type (priv->formats, mime_type), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-  return GDK_CLIPBOARD_GET_CLASS (clipboard)->read (clipboard, mime_type);
+  return GDK_CLIPBOARD_GET_CLASS (clipboard)->read_finish (clipboard, out_mime_type, result, error);
 }
 
 GdkClipboard *
index 6c36c940f92f2308b1698da50b326b251fe288f1..5721fd10fff5f6a377b068f6678eb7fccc5da590 100644 (file)
@@ -43,8 +43,17 @@ GDK_AVAILABLE_IN_3_94
 GdkContentFormats *     gdk_clipboard_get_formats       (GdkClipboard          *clipboard);
 
 GDK_AVAILABLE_IN_3_94
-GInputStream *          gdk_clipboard_read              (GdkClipboard          *clipboard,
-                                                         const char            *mime_type);
+void                    gdk_clipboard_read_async        (GdkClipboard          *clipboard,
+                                                         const char           **mime_types,
+                                                         int                    io_priority,
+                                                         GCancellable          *cancellable,
+                                                         GAsyncReadyCallback    callback,
+                                                         gpointer               user_data);
+GDK_AVAILABLE_IN_3_94
+GInputStream *          gdk_clipboard_read_finish       (GdkClipboard          *clipboard,
+                                                         const char           **out_mime_type,
+                                                         GAsyncResult          *result,
+                                                         GError               **error);
 
 G_END_DECLS
 
index faf1ce61fc50772175677acd643d4d6bba63a40c..b171068a5fca961b7c6edd6ec91e1d1dee9450d2 100644 (file)
@@ -41,8 +41,16 @@ struct _GdkClipboardClass
   void                  (* changed)                             (GdkClipboard           *clipboard);
 
   /* vfuncs */
-  GInputStream *        (* read)                                (GdkClipboard           *clipboard,
-                                                                 const char             *mime_type);
+  void                  (* read_async)                          (GdkClipboard          *clipboard,
+                                                                 GdkContentFormats     *formats,
+                                                                 int                    io_priority,
+                                                                 GCancellable          *cancellable,
+                                                                 GAsyncReadyCallback    callback,
+                                                                 gpointer               user_data);
+  GInputStream *        (* read_finish)                         (GdkClipboard          *clipboard,
+                                                                 const char           **out_mime_type,
+                                                                 GAsyncResult          *result,
+                                                                 GError               **error);
 };
 
 GdkClipboard *          gdk_clipboard_new                       (GdkDisplay             *display);
index b69200679315895f9eff1c3912748c1519295a48..a88c86d1ce33baf76f7f909a20c7c5e1031aab7d 100644 (file)
@@ -73,10 +73,16 @@ gdk_x11_clipboard_request_targets_finish (GObject      *source_object,
   guint i, n_atoms;
 
   bytes = g_input_stream_read_bytes_finish (stream, res, &error);
-  if (bytes == NULL || g_bytes_get_size (bytes) == 0)
+  if (bytes == NULL)
     {
-      if (bytes)
-        g_bytes_unref (bytes);
+      g_error_free (error);
+      g_object_unref (stream);
+      g_object_unref (cb);
+      return;
+    }
+  else if (g_bytes_get_size (bytes) == 0)
+    {
+      g_bytes_unref (bytes);
       g_object_unref (stream);
       g_object_unref (cb);
       return;
@@ -108,24 +114,43 @@ gdk_x11_clipboard_request_targets_finish (GObject      *source_object,
 }
 
 static void
-gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb)
+gdk_x11_clipboard_request_targets_got_stream (GObject      *source,
+                                              GAsyncResult *result,
+                                              gpointer      data)
 {
+  GdkX11Clipboard *cb = data;
   GInputStream *stream;
   GdkDisplay *display;
-  
-  display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+  GError *error = NULL;
+
+  stream = gdk_x11_selection_input_stream_new_finish (result, &error);
+  if (stream == NULL)
+    {
+      g_object_unref (cb);
+      return;
+    }
 
-  stream = gdk_x11_selection_input_stream_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
-                                               cb->selection,
-                                               "TARGETS",
-                                               cb->timestamp);
+  display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
 
   g_input_stream_read_bytes_async (stream,
                                    SELECTION_MAX_SIZE (display),
                                    G_PRIORITY_DEFAULT,
                                    NULL,
                                    gdk_x11_clipboard_request_targets_finish,
-                                   g_object_ref (cb));
+                                   cb);
+}
+
+static void
+gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb)
+{
+  gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
+                                            cb->selection,
+                                            "TARGETS",
+                                            cb->timestamp,
+                                            G_PRIORITY_DEFAULT,
+                                            NULL,
+                                            gdk_x11_clipboard_request_targets_got_stream,
+                                            g_object_ref (cb));
 }
 
 static GdkFilterReturn
@@ -180,16 +205,90 @@ gdk_x11_clipboard_finalize (GObject *object)
   G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object);
 }
 
-static GInputStream *
-gdk_x11_clipboard_read (GdkClipboard *clipboard,
-                        const char   *mime_type)
+static void
+gdk_x11_clipboard_read_got_stream (GObject      *source,
+                                   GAsyncResult *res,
+                                   gpointer      data)
+{
+  GTask *task = data;
+  GError *error = NULL;
+  GInputStream *stream;
+  
+  stream = gdk_x11_selection_input_stream_new_finish (res, &error);
+  /* XXX: We could try more types here */
+  if (stream == NULL)
+    g_task_return_error (task, error);
+  else
+    g_task_return_pointer (task, stream, g_object_unref);
+
+  g_object_unref (task);
+}
+
+static void
+gdk_x11_clipboard_read_async (GdkClipboard        *clipboard,
+                              GdkContentFormats   *formats,
+                              int                  io_priority,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
 {
   GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
+  const char * const *mime_types;
+  GTask *task;
+
+  task = g_task_new (clipboard, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gdk_x11_clipboard_read_async);
+  g_task_set_task_data (task, gdk_content_formats_ref (formats), (GDestroyNotify) gdk_content_formats_unref);
+
+  /* XXX: Sort differently? */
+  mime_types = gdk_content_formats_get_mime_types (formats, NULL);
+
+  gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
+                                            cb->selection,
+                                            mime_types[0],
+                                            cb->timestamp,
+                                            io_priority,
+                                            cancellable,
+                                            gdk_x11_clipboard_read_got_stream,
+                                            task);
+}
+
+static GInputStream *
+gdk_x11_clipboard_read_finish (GdkClipboard  *clipboard,
+                               const char   **out_mime_type,
+                               GAsyncResult  *result,
+                               GError       **error)
+{
+  GInputStream *stream;
+  GTask *task;
+
+  g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL);
+  task = G_TASK (result);
+  g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_clipboard_read_async, NULL);
+
+  stream = g_task_propagate_pointer (task, error);
+
+  if (stream)
+    {
+      if (out_mime_type)
+        {
+          GdkContentFormats *formats;
+          const char * const  *mime_types;
+
+          formats = g_task_get_task_data (task);
+          mime_types = gdk_content_formats_get_mime_types (formats, NULL);
+          *out_mime_type = mime_types[0];
+        }
+      g_object_ref (stream);
+    }
+  else
+    {
+      if (out_mime_type)
+        *out_mime_type = NULL;
+    }
 
-  return gdk_x11_selection_input_stream_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
-                                             cb->selection,
-                                             mime_type,
-                                             cb->timestamp);
+  return stream;
 }
 
 static void
@@ -200,7 +299,8 @@ gdk_x11_clipboard_class_init (GdkX11ClipboardClass *class)
 
   object_class->finalize = gdk_x11_clipboard_finalize;
 
-  clipboard_class->read = gdk_x11_clipboard_read;
+  clipboard_class->read_async = gdk_x11_clipboard_read_async;
+  clipboard_class->read_finish = gdk_x11_clipboard_read_finish;
 }
 
 static void
index 187b00ea848776d8ffcf9f0bf180096b732c1735..d4d698c7d80d9cef9b7a66738cd93e43a2fee792 100644 (file)
@@ -404,42 +404,64 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
 
     case SelectionNotify:
       {
+        GTask *task;
 
+        /* selection is not for us */
         if (priv->xselection != xevent->xselection.selection ||
             priv->xtarget != xevent->xselection.target)
           return GDK_FILTER_CONTINUE;
 
+        /* We already received a selectionNotify before */
+        if (priv->pending_task == NULL || 
+            g_task_get_source_tag (priv->pending_task) != gdk_x11_selection_input_stream_new_async)
+          return GDK_FILTER_CONTINUE;
+
         GDK_NOTE(SELECTION, g_print ("%s:%s: got SelectionNotify\n", priv->selection, priv->target));
 
-        if (xevent->xselection.property != None)
-          bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format);
-        else
-          bytes = NULL;
+        task = priv->pending_task;
+        priv->pending_task = NULL;
 
-        if (bytes == NULL)
-          { 
+        if (xevent->xselection.property == None)
+          {
+            g_task_return_new_error (task,
+                                     G_IO_ERROR,
+                                     G_IO_ERROR_NOT_FOUND,
+                                     _("Format %s not supported"), priv->target);
             gdk_x11_selection_input_stream_complete (stream);
           }
         else
           {
-            if (type == gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR"))
-              {
-                /* The remainder of the selection will come through PropertyNotify
-                   events on xwindow */
-                GDK_NOTE(SELECTION, g_print ("%s:%s: initiating INCR transfer\n", priv->selection, priv->target));
-                priv->incr = TRUE;
-                gdk_x11_selection_input_stream_flush (stream);
+            bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format);
+
+            g_task_return_pointer (task, g_object_ref (stream), g_object_unref);
+
+            if (bytes == NULL)
+              { 
+                gdk_x11_selection_input_stream_complete (stream);
               }
             else
               {
-                g_async_queue_push (priv->chunks, bytes);
-
-                gdk_x11_selection_input_stream_complete (stream);
+                if (priv->xtype == gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR"))
+                  {
+                    /* The remainder of the selection will come through PropertyNotify
+                       events on xwindow */
+                    GDK_NOTE(SELECTION, g_print ("%s:%s: initiating INCR transfer\n", priv->selection, priv->target));
+                    priv->incr = TRUE;
+                    gdk_x11_selection_input_stream_flush (stream);
+                  }
+                else
+                  {
+                    g_async_queue_push (priv->chunks, bytes);
+
+                    gdk_x11_selection_input_stream_complete (stream);
+                  }
               }
 
             XDeleteProperty (xdisplay, xwindow, xevent->xselection.property);
           }
-        }
+
+        g_object_unref (task);
+      }
       return GDK_FILTER_REMOVE;
 
     default:
@@ -447,15 +469,19 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
     }
 }
 
-GInputStream *
-gdk_x11_selection_input_stream_new (GdkDisplay *display,
-                                    const char *selection,
-                                    const char *target,
-                                    guint32     timestamp)
+void
+gdk_x11_selection_input_stream_new_async (GdkDisplay          *display,
+                                          const char          *selection,
+                                          const char          *target,
+                                          guint32              timestamp,
+                                          int                  io_priority,
+                                          GCancellable        *cancellable,
+                                          GAsyncReadyCallback  callback,
+                                          gpointer             user_data)
 {
   GdkX11SelectionInputStream *stream;
   GdkX11SelectionInputStreamPrivate *priv;
-  
+
   stream = g_object_new (GDK_TYPE_X11_SELECTION_INPUT_STREAM, NULL);
   priv = gdk_x11_selection_input_stream_get_instance_private (stream);
 
@@ -477,6 +503,25 @@ gdk_x11_selection_input_stream_new (GdkDisplay *display,
                      GDK_X11_DISPLAY (display)->leader_window,
                      timestamp);
 
-  return g_object_ref (stream);
+  priv->pending_task = g_task_new (NULL, cancellable, callback, user_data);
+  g_task_set_source_tag (priv->pending_task, gdk_x11_selection_input_stream_new_async);
+  g_task_set_priority (priv->pending_task, io_priority);
+}
+
+GInputStream *
+gdk_x11_selection_input_stream_new_finish (GAsyncResult  *result,
+                                           GError       **error)
+{
+  GInputStream *stream;
+  GTask *task;
+
+  g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
+  task = G_TASK (result);
+  g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_selection_input_stream_new_async, NULL);
+
+  stream = g_task_propagate_pointer (task, error);
+  if (stream)
+    g_object_ref (stream);
+  return stream;
 }
 
index ad4364f25c5031a97cf25836bd00b9e12f1dd7d0..c8bd19d55e8c5211ca39f08728a9ee047a3a889e 100644 (file)
@@ -50,10 +50,17 @@ struct GdkX11SelectionInputStreamClass
 
 GType          gdk_x11_selection_input_stream_get_type      (void) G_GNUC_CONST;
 
-GInputStream * gdk_x11_selection_input_stream_new           (GdkDisplay *display,
-                                                             const char *selection,
-                                                             const char *target,
-                                                             guint32     timestamp);
+void           gdk_x11_selection_input_stream_new_async     (GdkDisplay                 *display,
+                                                             const char                 *selection,
+                                                             const char                 *target,
+                                                             guint32                     timestamp,
+                                                             int                         io_priority,
+                                                             GCancellable               *cancellable,
+                                                             GAsyncReadyCallback         callback,
+                                                             gpointer                    user_data);
+GInputStream * gdk_x11_selection_input_stream_new_finish    (GAsyncResult               *result,
+                                                             GError                    **error);
+
 
 G_END_DECLS
 
index 7cabd6a0ea22480b81d12187f1cce21674255608..e141ecfcdc501d9a45ad7c8cab1f40fd58d616eb 100644 (file)
@@ -49,6 +49,30 @@ pixbuf_loaded_cb (GObject      *stream,
   g_object_unref (pixbuf);
 }
 
+static void
+clipboard_read_pixbuf_cb (GObject      *clipboard,
+                          GAsyncResult *result,
+                          gpointer      image)
+{
+  GInputStream *stream;
+  GError *error = NULL;
+
+  stream = gdk_clipboard_read_finish (GDK_CLIPBOARD (clipboard),
+                                      NULL,
+                                      result,
+                                      &error);
+  if (stream)
+    {
+      gdk_pixbuf_new_from_stream_async (stream, NULL, pixbuf_loaded_cb, image);
+      g_object_unref (stream);
+    }
+  else
+    {
+      g_print ("%s\n", error->message);
+      g_error_free (error);
+    }
+}
+
 static void
 visible_child_changed_cb (GtkWidget    *stack,
                           GParamSpec   *pspec,
@@ -63,19 +87,13 @@ visible_child_changed_cb (GtkWidget    *stack,
   else if (g_str_equal (visible_child, "image"))
     {
       GtkWidget *image = gtk_stack_get_child_by_name (GTK_STACK (stack), "image");
-      GdkContentFormats *formats;
-      GInputStream *stream;
-
-      formats = gdk_clipboard_get_formats (clipboard);
-      if (gdk_content_formats_contain_mime_type (formats, "image/png"))
-        stream = gdk_clipboard_read (clipboard, "image/png");
-      else
-        stream = NULL;
-      if (stream)
-        {
-          gdk_pixbuf_new_from_stream_async (stream, NULL, pixbuf_loaded_cb, image);
-          g_object_unref (stream);
-        }
+
+      gdk_clipboard_read_async (clipboard,
+                                (const gchar*[2]) { "image/png", NULL },
+                                G_PRIORITY_DEFAULT,
+                                NULL,
+                                clipboard_read_pixbuf_cb,
+                                image);
     }
 }